package org.jeecg.modules.test.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.BulkRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.api.query.ElasticsearchQuery;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.modules.test.service.IElasticsearchCommonService;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Elasticsearch通用serviceImpl
 *
 * @author ZhangYu
 * @date 2024/9/10 20:52
 */
@Slf4j
@Service
public class ElasticsearchCommonServiceImpl implements IElasticsearchCommonService {

    @Resource
    private ElasticsearchClient elasticsearchClient;

    /**
     * id字段
     */
    private final static String ID_FIELD = "id";

    @Override
    public <T> void save(@NotNull T entity, String tableName) {
        try {
            String idValue = getIdValue(entity);
            //新增文档，需要设置id。不设置id将新增，id将自动生成。没有对应id的文档就创建，有就覆盖更新所有字段（相当于先删除再新增）并更新版本号
            elasticsearchClient.index(builder -> builder.index(tableName).id(idValue).document(entity));
        } catch (IOException | NoSuchFieldException | IllegalAccessException e) {
            throw new JeecgBootException("Elasticsearch插入一条记录出现异常", e);
        }
    }

    /**
     * 反射获取id值
     *
     * @param entity 实体类
     * @return 字符串id值
     */
    private static <T> String getIdValue(@NotNull T entity) throws NoSuchFieldException, IllegalAccessException {
        Field idField = entity.getClass().getDeclaredField(ID_FIELD);
        Assert.notNull(idField, "错误：ID字段不存在");
        idField.setAccessible(true);
        return idField.get(entity).toString();
    }

    @Override
    public <T> void saveBatch(@NotNull List<T> entityList, String tableName) {
        if (CollectionUtil.isEmpty(entityList)) {
            return;
        }
        Assert.notNull(tableName, "错误：索引不能为空");
        try {
            BulkRequest.Builder builder = new BulkRequest.Builder().index(tableName);
            for (Object object : entityList) {
                String idValue = getIdValue(object);
                //设置文档信息
                builder.operations(item -> item.index(v -> v.id(String.valueOf(idValue)).document(object)));
            }
            BulkRequest request = builder.build();
            elasticsearchClient.bulk(request);
        } catch (IOException | NoSuchFieldException | IllegalAccessException e) {
            throw new JeecgBootException("Elasticsearch批量保存出现异常", e);
        }
    }

    @Override
    public void removeById(Serializable id, String tableName) {
        try {
            elasticsearchClient.delete(builder -> builder.index(tableName).id(id.toString()));
        } catch (IOException e) {
            throw new JeecgBootException("Elasticsearch根据ID删除出现异常", e);
        }
    }

    @Override
    public void remove(@NotNull ElasticsearchQuery elasticsearchQuery, String tableName) {
        Assert.notEmpty(elasticsearchQuery.getAllMap(), "错误：columnMap不能为空");
        try {
            //查询删除，根据查询结果删除数据
            elasticsearchClient.deleteByQuery(deleteByQueryRequestBuilder -> deleteByQueryRequestBuilder.index(tableName)
                    .query(queryBuilder -> queryBuilder.term(termQueryBuilder -> {
                        Map<Object, Object> hashMap = Optional.ofNullable(elasticsearchQuery.getAllMap().get(ElasticsearchQuery.EQ)).orElse(Collections.emptyMap());
                        for (Map.Entry<Object, Object> entry : hashMap.entrySet()) {
                            String mapKey = entry.getKey().toString();
                            String mapValue = entry.getValue().toString();
                            termQueryBuilder.field(mapKey).value(mapValue);
                        }
                        return termQueryBuilder;
                    }))
            );
        } catch (IOException e) {
            throw new JeecgBootException("Elasticsearch根据ID删除出现异常", e);
        }
    }

    @Override
    public <T> void updateById(T entity, String tableName) {
        try {
            String idValue = getIdValue(entity);
            //修改文档，只修改设置的字段，文档不存在时插入
            elasticsearchClient.update(builder -> builder.index(tableName).id(idValue).doc(entity).docAsUpsert(true), entity.getClass());
        } catch (IOException | NoSuchFieldException | IllegalAccessException e) {
            throw new JeecgBootException("Elasticsearch插入一条记录出现异常", e);
        }
    }

    @Override
    public <T> T getById(Serializable id, String tableName, final Class<T> newClass) {
        T entity;
        try {
            //根据id查询文档
            entity = elasticsearchClient.get(builder -> builder.index(tableName).id(id.toString()), newClass).source();
        } catch (IOException e) {
            throw new JeecgBootException("Elasticsearch根据ID查询文档出现异常", e);
        }
        return entity;
    }

    @Override
    public <N, O> Page<N> setPageInfo(Integer pageNo, Integer pageSize, @NotNull SearchResponse<O> searchResponse, final Class<N> newClass) {
        //es分页信息
        HitsMetadata<O> hitsMetadata = searchResponse.hits();
        List<Hit<O>> hitList = hitsMetadata.hits();
        //设置分页信息
        Page<N> newPage = new Page<>();
        newPage.setSize(pageSize);
        newPage.setCurrent(pageNo);
        if (ObjectUtil.isEmpty(hitsMetadata.total())) {
            return newPage;
        }
        Assert.notNull(hitsMetadata.total(), "错误：hitsMetadata.total为空");
        newPage.setTotal(hitsMetadata.total().value());
        newPage.setRecords(hitList.stream().map(h -> BeanUtil.copyProperties(h.source(), newClass)).collect(Collectors.toList()));
        return newPage;
    }

}
